﻿using CMS.Util;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;

namespace CMS.REST
{

    public static class RESTClient
    {
        /// <summary>
        /// 主机
        /// </summary>
        public static string HOST { get; set; } = "http://cms.computer.bitzh.edu.cn/index.php/";

        /// <summary>
        /// 公共的Cookie存储容器
        /// </summary>
        public static CookieContainer Cookies { get; set; } = new CookieContainer();

        /// <summary>
        /// 公共的头
        /// </summary>
        public static IDictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();

        /// <summary>
        /// 正在确认是否重登陆时,抛出此事件
        /// </summary>
        public static event EventHandler<CancelEventArgs> OpeningRelogin;

        /// <summary>
        /// 当触发重新登陆时,触发此事件
        /// </summary>
        public static event EventHandler ShouldRelogin;

        #region 返回码定义
        /// <summary>
        /// 执行成功
        /// </summary>
        public const int STATUS_OK = 200;

        /// <summary>
        /// 接口调用失败
        /// </summary>
        public const int STATUS_FAIL = 400;

        /// <summary>
        /// 接口没有权限
        /// </summary>
        public const int STATUS_AUTH = 401;

        /// <summary>
        /// 接口传入参数错误
        /// </summary>
        public const int STATUS_INVALD_PARAM = 402;

        /// <summary>
        /// 接口没有定义
        /// </summary>
        public const int STATUS_NOT_FOUND = 404;

        /// <summary>
        /// 接口没有权限调用
        /// </summary>
        public const int STATUS_FORBIDDEN = 405;

        /// <summary>
        /// 服务器错误
        /// </summary>
        public const int STATUS_FATAL = 500;

        #endregion

        #region 内部函数
        /// <summary>
        /// 获取一个Http 请求对象
        /// </summary>
        /// <param name="api"></param>
        /// <returns></returns>
        public static HttpWebRequest GetRequest(string api, string method)
        {
            Debug.WriteLine($"\r\nURL:{HOST + api}\r\nMethod:{method}\r\n", "REST Request0");
            HttpWebRequest request = WebRequest.CreateHttp(HOST + api);
            request.KeepAlive = false;
            request.CookieContainer = Cookies;
            request.Method = method;
            IEnumerator<KeyValuePair<string, string>> enumerator = Headers.GetEnumerator();
            while (enumerator.MoveNext())
            {
                request.Headers.Add(enumerator.Current.Key, enumerator.Current.Value);
            }
            return request;
        }

        /// <summary>
        /// 获取一个写入了指定的body的Http请求对象
        /// </summary>
        /// <param name="api">要调用的api</param>
        /// <param name="method">请求方法</param>
        /// <param name="body">请求体</param>
        /// <param name="contentType"></param>
        /// <returns></returns>
        public static HttpWebRequest GetRequest(string api, string method, string body, string contentType = "application/json")
        {
            Debug.WriteLine($"\r\nURL:{HOST + api}\r\nMethod:{method}\r\nContentType:{contentType}\r\nBody:{body}\r\n", "REST Request2");
            HttpWebRequest request = GetRequest(api, method);
            byte[] bodyBytes = Encoding.UTF8.GetBytes(body);
            request.ContentLength = bodyBytes.Length;
            request.ContentType = contentType;
            Stream stream = request.GetRequestStream();
            stream.Write(bodyBytes, 0, bodyBytes.Length);
            stream.Close();
            return request;
        }

        /// <summary>
        /// 获取一个在body中写入了指定Json对象的HTTP请求对象
        /// </summary>
        /// <param name="api"></param>
        /// <param name="method"></param>
        /// <param name="body"></param>
        /// <returns></returns>
        public static HttpWebRequest GetRequest(string api, string method, JContainer body)
        {
            return GetRequest(api, method, body.ToString(Formatting.None));
        }

        /// <summary>
        /// 获取HTTP响应,并转换成RESTResponse对象
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public static RESTResponse GetResponse(HttpWebRequest request)
        {
            try
            {
                using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
                {
                    using (StreamReader stream = new StreamReader(response.GetResponseStream()))
                    {
                        string data = stream.ReadToEnd();
                        Debug.WriteLine($"\r\nURL:{request.RequestUri}\r\nResponse:{data}\r\n", "REST Response");
                        return JsonConvert.DeserializeObject<RESTResponse>(data);
                    }
                }
            }
            catch (WebException err)
            {
                Debug.WriteLine($"\r\nURL:{request.RequestUri}\r\nError Message:{err.Message}\nStack Trace:{err.StackTrace}", "REST Response");
                using (StreamReader stream = new StreamReader(err.Response.GetResponseStream()))
                {
                    Debug.WriteLine(stream.ReadToEnd(), "REST Response");
                }
                return new RESTResponse(err);
            }
            catch (Exception err)
            {
                Debug.WriteLine($"\r\nURL:{request.RequestUri}\r\nError Message:{err.Message}\nStack Trace:{err.StackTrace}", "REST Response");
                return new RESTResponse(err);
            }
        }

        /// <summary>
        /// 处理可处理的异常并进行重试,直到成功或不可能成功
        /// </summary>
        /// <param name="func"></param>
        /// <returns></returns>
        public static RESTResponse HandleError(Func<RESTResponse> func)
        {
            while (true)
            {
                RESTResponse response = func();
                switch (response.Status)
                {
                    case STATUS_OK:
                        return response;    //成功,直接返回
                    case STATUS_FAIL:
                    case STATUS_NOT_FOUND:
                    case STATUS_FORBIDDEN:
                    case STATUS_FATAL:
                        return response;    //无法恢复的错误,直接返回响应
                    case STATUS_INVALD_PARAM:
                        return response;
                    //throw new Exception(response.ErrMsg);
                    case STATUS_AUTH:
                        //触发重新登录事件
                        if (OpeningRelogin != null)
                        {
                            CancelEventArgs args = new CancelEventArgs();
                            OpeningRelogin.Invoke(null, args);
                            if (args.Cancel)
                            {
                                return response;
                            }
                        }
                        if (ShouldRelogin != null)
                        {
                            ShouldRelogin.Invoke(null, EventArgs.Empty);
                        }
                        else
                        {
                            return response;
                        }
                        break;

                        //if (MessageBox.Show("您的账号在另一地点登陆,您已被迫下线,是否尝试重新登陆", "下线通知", MessageBoxButtons.YesNo) == DialogResult.Yes)
                        //    {

                        //    }


                }
            }
        }

        #endregion


        #region REST接口
        /// <summary>
        /// 普通的HTTP GET请求
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,不要以"/"开头!!!)</param>
        /// <returns></returns>
        public static RESTResponse HttpGet(string api)
        {
            return GetResponse(GetRequest(api, "GET"));
        }

        /// <summary>
        /// 普通的HTTP GET请求
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,不要以"/"开头!!!)</param>
        /// <param name="param">请求资源时的附带参数</param>
        /// <returns></returns>
        public static RESTResponse HttpGet(string api, IDictionary<string, string> param)
        {
            return GetResponse(GetRequest($"{api}?{string.Join("&", from i in param select $"{i.Key}={WebUtility.UrlEncode(i.Value)}")}", "GET"));
        }


        /// <summary>
        /// 普通的HTTP GET请求,自动处理可处理的错误
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,不要以"/"开头!!!)</param>
        /// <returns></returns>
        public static RESTResponse HttpGetManaged(string api)
        {
            return HandleError(() => HttpGet(api));
        }


        /// <summary>
        /// 普通的HTTP GET请求
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,不要以"/"开头!!!)</param>
        /// <param name="param">请求资源时的附带参数</param>
        /// <returns></returns>
        public static RESTResponse HttpGetManaged(string api, IDictionary<string, string> param)
        {
            return HandleError(() => HttpGet(api, param));
        }

        /// <summary>
        /// 普通的HTTP POST请求
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,第一个字符不是"/")</param>
        /// <param name="body">请求体数据</param>
        /// <returns></returns>
        public static RESTResponse HttpPost(string api, JContainer body)
        {
            return GetResponse(GetRequest(api, "POST", body));
        }

        /// <summary>
        /// 普通的HTTP POST请求,自动处理可处理的错误
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,第一个字符不是"/")</param>
        /// <param name="body">请求体数据</param>
        /// <returns></returns>
        public static RESTResponse HttpPostManaged(string api, JContainer body)
        {
            return HandleError(() => HttpPost(api, body));
        }

        /// <summary>
        /// 查询一系列数据
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,第一个字符不是"/")</param>
        /// <param name="where">请求资源的查询条件</param>
        /// <returns></returns>
        public static RESTResponse Index(string api, params RESTWhere[] where)
        {
            string param = string.Empty;
            if (where.Length > 0)
            {
                JArray array = JArray.FromObject(where);
                param = "filter=" + Utils.UrlEncode(array.ToString(Formatting.None));
            }
            if (param != string.Empty)
            {
                api += "?" + param;
            }
            return GetResponse(GetRequest(api, "GET"));
        }

        /// <summary>
        /// 查询一系列数据
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,第一个字符不是"/")</param>
        /// <param name="visibleField">请求资源的字段名</param>
        /// <param name="where">请求资源的查询条件</param>
        /// <returns></returns>
        public static RESTResponse Index(string api, string[] visibleField, params RESTWhere[] where)
        {
            string param = string.Empty;
            if (where.Length > 0)
            {
                JArray array = JArray.FromObject(where);
                param = "filter=" + Utils.UrlEncode(array.ToString(Formatting.None));
            }
            {
                JArray array = JArray.FromObject(visibleField);
                param += (param == string.Empty ? "visible=" : "&visible=") + Utils.UrlEncode(array.ToString(Formatting.None));
            }
            if (param != string.Empty)
            {
                api += "?" + param;
            }
            return GetResponse(GetRequest(api, "GET"));
        }

        /// <summary>
        /// 查询一系列数据,自动处理可处理的错误
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,第一个字符不是"/")</param>
        /// <param name="where">请求资源的查询条件</param>
        /// <returns></returns>
        public static RESTResponse IndexManaged(string api, params RESTWhere[] where)
        {
            return HandleError(() => Index(api, where));
        }

        /// <summary>
        /// 查询一系列数据,自动处理可处理的错误
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,第一个字符不是"/")</param>
        /// <param name="visibleField">请求资源的字段名</param>
        /// <param name="where">请求资源的查询条件</param>
        /// <returns></returns>
        public static RESTResponse IndexManaged(string api, string[] visibleField, params RESTWhere[] where)
        {
            return HandleError(() => Index(api, visibleField, where));
        }

        /// <summary>
        /// 读取一个资源
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,第一个字符不是"/")</param>
        /// <param name="id">要读取的值的主键的值</param>
        /// <returns></returns>
        public static RESTResponse Read(string api, string id)
        {
            return GetResponse(GetRequest($"{api}/{id}", "GET"));
        }


        /// <summary>
        /// 读取一个资源,自动处理可处理的错误
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,第一个字符不是"/")</param>
        /// <param name="id">要读取的值的主键的值</param>
        /// <returns></returns>
        public static RESTResponse ReadManaged(string api, string id)
        {
            return HandleError(() => Read(api, id));
        }

        /// <summary>
        /// 保存一个资源,并返回其新的ID
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,第一个字符不是"/")</param>
        /// <param name="json">要保存的数据</param>
        /// <returns></returns>
        public static RESTResponse Save(string api, JObject json)
        {
            return GetResponse(GetRequest(api, "POST", json));
        }

        /// <summary>
        /// 保存一个资源,并返回其新的ID,自动处理可处理的错误
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,第一个字符不是"/")</param>
        /// <param name="json">要保存的数据</param>
        /// <returns></returns>
        public static RESTResponse SaveManaged(string api, JObject json)
        {
            return HandleError(() => Save(api, json));
        }

        /// <summary>
        /// 保存多个资源
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,第一个字符不是"/")</param>
        /// <param name="jsonData">要保存的数据</param>
        /// <returns>data永无数据,只能判断是否成功</returns>
        public static RESTResponse Saves(string api, JArray json)
        {
            return GetResponse(GetRequest(api + "/many", "POST", json));
        }

        /// <summary>
        /// 保存多个资源,自动处理可处理的错误
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,第一个字符不是"/")</param>
        /// <param name="jsonData">要保存的数据</param>
        /// <returns>data永无数据,只能判断是否成功</returns>
        public static RESTResponse SavesManaged(string api, JArray json)
        {
            return HandleError(() => Saves(api, json));
        }


        /// <summary>
        /// 更新一个列表(列表的通过where进行查找,并以data替换之)
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,第一个字符不是"/")</param>
        /// <param name="data">要提交的数据</param>
        /// <param name="where">符合条件的数据将会先被删除,然后写入data传入的数据</param>
        /// <returns></returns>
        public static RESTResponse SaveList(string api, JArray data, params RESTWhere[] where)
        {
            JObject param = new JObject();
            if (where.Length > 0)
            {
                JArray array = JArray.FromObject(where);
                param.Add("where", array);
            }
            param.Add("data", data);
            return GetResponse(GetRequest(api + "/list", "POST", param));
        }

        /// <summary>
        /// 更新一个列表(列表的通过where进行查找,并以data替换之),自动处理可处理的错误
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,第一个字符不是"/")</param>
        /// <param name="data">要提交的数据</param>
        /// <param name="where">符合条件的数据将会先被删除,然后写入data传入的数据</param>
        /// <returns></returns>
        public static RESTResponse SaveListManaged(string api, JArray data, params RESTWhere[] where)
        {
            return HandleError(() => SaveList(api, data, where));
        }


        /// <summary>
        /// 更新一个资源的数据,支持修改部分数据
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,第一个字符不是"/")</param>
        /// <param name="id">要更新的资源的ID</param>
        /// <param name="json">要更新的字段和其值的映射</param>
        /// <returns></returns>
        public static RESTResponse Update(string api, string id, JObject json)
        {
            return GetResponse(GetRequest($"{api}/{id}", "PUT", json));
        }

        /// <summary>
        /// 更新一个资源的数据,支持修改部分数据,自动处理可处理的错误
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,第一个字符不是"/")</param>
        /// <param name="id">要更新的资源的ID</param>
        /// <param name="json">要更新的字段和其值的映射</param>
        /// <returns></returns>
        public static RESTResponse UpdateManaged(string api, string id, JObject json)
        {
            return HandleError(() => Update(api, id, json));
        }


        /// <summary>
        /// 更新多个资源,支持修改部分数据,但是json中一定要携带主键
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,第一个字符不是"/")</param>
        /// <param name="json">要提交的字段和值的映射的数组,其中必须包含主键</param>
        /// <returns></returns>
        public static RESTResponse Updates(string api, JArray json)
        {
            return GetResponse(GetRequest(api, "PUT", json));
        }


        /// <summary>
        /// 更新多个资源,支持修改部分数据,但是json中一定要携带主键,自动处理可处理的错误
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,第一个字符不是"/")</param>
        /// <param name="json">要提交的字段和值的映射的数组,其中必须包含主键</param>
        /// <returns></returns>
        public static RESTResponse UpdatesManaged(string api, JArray json)
        {
            return HandleError(() => Updates(api, json));
        }


        /// <summary>
        /// 删除一个资源
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,第一个字符不是"/")</param>
        /// <param name="id">要删除的数据的主键</param>
        /// <returns></returns>
        public static RESTResponse Delete(string api, string id)
        {
            return GetResponse(GetRequest($"{api}/{id}", "DELETE"));
        }

        /// <summary>
        /// 删除一个资源,自动处理可处理的错误
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,第一个字符不是"/")</param>
        /// <param name="id">要删除的数据的主键</param>
        /// <returns></returns>
        public static RESTResponse DeleteManaged(string api, string id)
        {
            return HandleError(() => Delete(api, id));
        }


        /// <summary>
        /// 删除多个资源
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,第一个字符不是"/")</param>
        /// <param name="id">要删除的资源的id</param>
        /// <returns></returns>
        public static RESTResponse Deletes(string api, params string[] ids)
        {
            return GetResponse(GetRequest($"{api}/{string.Join(",", ids)}", "DELETE"));
        }

        /// <summary>
        /// 删除多个资源,自动处理可处理的错误
        /// </summary>
        /// <param name="api">请求的资源api(不带域名和协议,第一个字符不是"/")</param>
        /// <param name="ids">要删除的所有资源id</param>
        /// <returns></returns>
        public static RESTResponse DeletesManaged(string api, params string[] ids)
        {
            return HandleError(() => Deletes(api, ids));
        }

        #endregion

        #region 数据设置
        /// <summary>
        /// 设置AuthKey
        /// </summary>
        /// <param name="authKey"></param>
        public static void SetAuthKey(string authKey)
        {
            Headers["authKey"] = authKey;
        }

        #endregion

        #region 服务器时间获取接口(存在可接受范围内的误差)
        /// <summary>
        /// 本地和服务器的时间差异
        /// </summary>
        private static int _TimeDiff = 0;

        /// <summary>
        /// 同步的时刻,用于确定是否重新同步
        /// </summary>
        private static int _SyncTick = 0;

        /// <summary>
        /// 同步的间隔(ms)
        /// </summary>
        private const int SyncInterval = 300 * 1000;

        /// <summary>
        /// 获取当前服务的时间(存在可接受范围内的误差)
        /// </summary>
        /// <returns></returns>
        public static int GetServerTimeStamp()
        {
            if (Environment.TickCount - _SyncTick > SyncInterval || _SyncTick == 0)
            {
                RESTResponse resp = HttpGet("apis/v1/timestamp");
                if (resp.IsSuccess)
                {
                    int ret = resp.Data[0].ToObject<int>();

                    _TimeDiff = Utils.GetTimeStamp() - ret;
                    _SyncTick = Environment.TickCount;
                    return ret;
                }
                else
                {
                    return Utils.GetTimeStamp();
                }
            }
            else
            {
                return Utils.GetTimeStamp() - _TimeDiff;
            }
        }
        #endregion
    }
}
